﻿using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace OverStore.Runtime.Storage
{
    /// <summary>
    /// Represents collection of values accessible using case-insensitive names.
    /// </summary>
    /// <seealso cref="TypedRecord"/>
    public interface IRecord
    {
        /// <summary>
        /// Try to get result with specified name. If result with such name exisits, method returns true,
        /// otherwise false. Found result is returned via <paramref name="result"/> output parameter.
        /// </summary>
        /// <param name="name">The case-insensitive result name.</param>
        /// <param name="result">Found result or null if result with specified name does not exist.</param>
        /// <returns>
        ///     <c>true</c> if result with such name exists, <c>false</c> otherwise.
        /// </returns>
        bool TryGetValue(string name, out object result);
    }

    /// <summary>
    /// Allows to access values using case insensitive names without boxing.
    /// </summary>
    public abstract class TypedRecord : IRecord
    {
        /// <summary>
        /// Try to get result with specified name. If result with such name exisits, method returns true,
        /// otherwise false. Found result is returned via <paramref name="result"/> output parameter.
        /// </summary>
        /// <param name="name">The case-insensitive result name.</param>
        /// <param name="result">Found result or null if result with specified name does not exist.</param>
        /// <returns>
        /// 	<c>true</c> if result with such name exists, <c>false</c> otherwise.
        /// </returns>
        public abstract bool TryGetValue(string name, out object result);

        /// <summary>
        /// Gets the boolean result with specified name.
        /// </summary>
        public abstract bool GetBoolean(string name);

        /// <summary>
        /// Gets the nullable boolean result with specified name.
        /// </summary>
        public abstract Nullable<bool> GetNullableBoolean(string name);

        /// <summary>
        /// Gets the byte result with specified name.
        /// </summary>
        public abstract byte GetByte(string name);

        /// <summary>
        /// Gets the nullable byte result with specified name.
        /// </summary>
        public abstract Nullable<byte> GetNullableByte(string name);

        /// <summary>
        /// Gets the array of bytes with specified name.
        /// </summary>
        public abstract byte[] GetArrayOfByte(string name);

        /// <summary>
        /// Gets the char result with specified name.
        /// </summary>
        public abstract char GetChar(string name);

        /// <summary>
        /// Gets the nullable char with specified name.
        /// </summary>
        public abstract Nullable<char> GetNullableChar(string name);

        /// <summary>
        /// Gets the GetDateTime result with specified name.
        /// </summary>
        public abstract DateTime GetDateTime(string name);

        /// <summary>
        /// Gets the nullable GetDateTime result with specified name.
        /// </summary>
        public abstract Nullable<DateTime> GetNullableDateTime(string name);

        /// <summary>
        /// Gets the decimal result with specified name.
        /// </summary>
        public abstract decimal GetDecimal(string name);

        /// <summary>
        /// Gets the nullable decimal result with specified name.
        /// </summary>
        public abstract Nullable<decimal> GetNullableDecimal(string name);

        /// <summary>
        /// Gets the double result with specified name.
        /// </summary>
        public abstract double GetDouble(string name);

        /// <summary>
        /// Gets the nullable double result with specified name.
        /// </summary>
        public abstract Nullable<double> GetNullableDouble(string name);

        /// <summary>
        /// Gets the single result with specified name.
        /// </summary>
        public abstract float GetSingle(string name);

        /// <summary>
        /// Gets the nullable single result with specified name.
        /// </summary>
        public abstract Nullable<float> GetNullableSingle(string name);

        /// <summary>
        /// Gets the GetGuid result with specified name.
        /// </summary>
        public abstract Guid GetGuid(string name);

        /// <summary>
        /// Gets the nullable guid result with specified name.
        /// </summary>
        public abstract Nullable<Guid> GetNullableGuid(string name);

        /// <summary>
        /// Gets the Int32 result with specified name.
        /// </summary>
        public abstract int GetInt32(string name);

        /// <summary>
        /// Gets the Nullable{Int32} result with specified name.
        /// </summary>
        public abstract int? GetNullableInt32(string name);

        /// <summary>
        /// Gets the Int64 result with specified name.
        /// </summary>
        public abstract long GetInt64(string name);

        /// <summary>
        /// Gets the Nullable{Int64} result with specified name.
        /// </summary>
        public abstract long? GetNullableInt64(string name);

        /// <summary>
        /// Gets the string result with specified name.
        /// </summary>
        public abstract string GetString(string name);

        /// <summary>
        /// Gets the result with specified name.
        /// </summary>
        public abstract object GetValue(string name);

        /// <summary>
        /// Determines whether the current instance contains result with the specified name.
        /// </summary>
        public abstract bool ContainsName(string name);
    }

    /// <summary>
    ///     <see cref="TypedRecord"/> implementation without values.
    /// </summary>
    public sealed class EmptyTypedRecord : TypedRecord
    {
        /// <summary>
        /// Singleton instance of <see cref="EmptyTypedRecord"/> class.
        /// </summary>
        public static readonly TypedRecord Value = new EmptyTypedRecord();

        private EmptyTypedRecord()
        {
        }

        #region TypedRecord Members

        /// <summary>
        /// Gets the boolean result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override bool GetBoolean(string name)
        {
            throw new ArgumentException("Value with specified name does not exist.");
        }

        /// <summary>
        /// Gets the nullable boolean result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override bool? GetNullableBoolean(string name)
        {
            throw new ArgumentException("Value with specified name does not exist.");
        }

        /// <summary>
        /// Gets the byte result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override byte GetByte(string name)
        {
            throw new ArgumentException("Value with specified name does not exist.");
        }

        /// <summary>
        /// Gets the nullable byte result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override byte? GetNullableByte(string name)
        {
            throw new ArgumentException("Value with specified name does not exist.");
        }

        /// <summary>
        /// Gets the array of bytes with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override byte[] GetArrayOfByte(string name)
        {
            throw new ArgumentException("Value with specified name does not exist.");
        }

        /// <summary>
        /// Gets the char result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override char GetChar(string name)
        {
            throw new ArgumentException("Value with specified name does not exist.");
        }

        /// <summary>
        /// Gets the nullable char with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override char? GetNullableChar(string name)
        {
            throw new ArgumentException("Value with specified name does not exist.");
        }

        /// <summary>
        /// Gets the GetDateTime result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override DateTime GetDateTime(string name)
        {
            throw new ArgumentException("Value with specified name does not exist.");
        }

        /// <summary>
        /// Gets the nullable GetDateTime result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override DateTime? GetNullableDateTime(string name)
        {
            throw new ArgumentException("Value with specified name does not exist.");
        }

        /// <summary>
        /// Gets the decimal result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override decimal GetDecimal(string name)
        {
            throw new ArgumentException("Value with specified name does not exist.");
        }

        /// <summary>
        /// Gets the nullable decimal result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override decimal? GetNullableDecimal(string name)
        {
            throw new ArgumentException("Value with specified name does not exist.");
        }

        /// <summary>
        /// Gets the double result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override double GetDouble(string name)
        {
            throw new ArgumentException("Value with specified name does not exist.");
        }

        /// <summary>
        /// Gets the nullable double result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override double? GetNullableDouble(string name)
        {
            throw new ArgumentException("Value with specified name does not exist.");
        }

        /// <summary>
        /// Gets the single result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override float GetSingle(string name)
        {
            throw new ArgumentException("Value with specified name does not exist.");
        }

        /// <summary>
        /// Gets the nullable single result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override float? GetNullableSingle(string name)
        {
            throw new ArgumentException("Value with specified name does not exist.");
        }

        /// <summary>
        /// Gets the GetGuid result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override Guid GetGuid(string name)
        {
            throw new ArgumentException("Value with specified name does not exist.");
        }

        /// <summary>
        /// Gets the nullable guid result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override Guid? GetNullableGuid(string name)
        {
            throw new ArgumentException("Value with specified name does not exist.");
        }

        /// <summary>
        /// Gets the Int32 result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override int GetInt32(string name)
        {
            throw new ArgumentException("Value with specified name does not exist.");
        }

        /// <summary>
        /// Gets the Nullable{Int32} result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override int? GetNullableInt32(string name)
        {
            throw new ArgumentException("Value with specified name does not exist.");
        }

        /// <summary>
        /// Gets the Int64 result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override long GetInt64(string name)
        {
            throw new ArgumentException("Value with specified name does not exist.");
        }

        /// <summary>
        /// Gets the Nullable{Int64} result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override long? GetNullableInt64(string name)
        {
            throw new ArgumentException("Value with specified name does not exist.");
        }

        /// <summary>
        /// Gets the string result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override string GetString(string name)
        {
            throw new ArgumentException("Value with specified name does not exist.");
        }

        /// <summary>
        /// Gets the result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override object GetValue(string name)
        {
            throw new ArgumentException("Value with specified name does not exist.");
        }

        /// <summary>
        /// Determines whether the current instance contains result with the specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override bool ContainsName(string name)
        {
            return false;
        }

        #endregion

        /// <summary>
        /// Try to get result with specified name. If result with such name exisits, method returns true,
        /// otherwise false. Found result is returned via <paramref name="result"/> output parameter.
        /// </summary>
        /// <param name="name">The case-insensitive result name.</param>
        /// <param name="result">Found result or null if result with specified name does not exist.</param>
        /// <returns>
        /// 	<c>true</c> if result with such name exists, <c>false</c> otherwise.
        /// </returns>
        public override bool TryGetValue(string name, out object result)
        {
            result = null;
            return false;
        }
    }

    /// <summary>
    /// Abstract class implements <see cref="TypedRecord"/> interface that uses TryGetValue method for implement all other methods.
    /// </summary>
    public abstract class TypedRecordBase : TypedRecord
    {
        /// <summary>
        /// Gets the boolean result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override bool GetBoolean(string name)
        {
            return (bool)GetValue(name);
        }

        /// <summary>
        /// Gets the nullable boolean result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override bool? GetNullableBoolean(string name)
        {
            return (bool?)GetValue(name);
        }

        /// <summary>
        /// Gets the byte result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override byte GetByte(string name)
        {
            return (byte)GetValue(name);
        }

        /// <summary>
        /// Gets the nullable byte result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override byte? GetNullableByte(string name)
        {
            return (byte?)GetValue(name);
        }

        /// <summary>
        /// Gets the array of bytes with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override byte[] GetArrayOfByte(string name)
        {
            return (byte[])GetValue(name);
        }

        /// <summary>
        /// Gets the char result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override char GetChar(string name)
        {
            return (char)GetValue(name);
        }

        /// <summary>
        /// Gets the nullable char with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override char? GetNullableChar(string name)
        {
            return (char?)GetValue(name);
        }

        /// <summary>
        /// Gets the GetDateTime result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override DateTime GetDateTime(string name)
        {
            return (DateTime)GetValue(name);
        }

        /// <summary>
        /// Gets the nullable GetDateTime result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override DateTime? GetNullableDateTime(string name)
        {
            return (DateTime?)GetValue(name);
        }

        /// <summary>
        /// Gets the decimal result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override decimal GetDecimal(string name)
        {
            return (decimal)GetValue(name);
        }

        /// <summary>
        /// Gets the nullable decimal result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override decimal? GetNullableDecimal(string name)
        {
            return (decimal?)GetValue(name);
        }

        /// <summary>
        /// Gets the double result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override double GetDouble(string name)
        {
            return (double)GetValue(name);
        }

        /// <summary>
        /// Gets the nullable double result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override double? GetNullableDouble(string name)
        {
            return (double?)GetValue(name);
        }

        /// <summary>
        /// Gets the single result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override float GetSingle(string name)
        {
            return (float)GetValue(name);
        }

        /// <summary>
        /// Gets the nullable single result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override float? GetNullableSingle(string name)
        {
            return (float?)GetValue(name);
        }

        /// <summary>
        /// Gets the GetGuid result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override Guid GetGuid(string name)
        {
            return (Guid)GetValue(name);
        }

        /// <summary>
        /// Gets the nullable guid result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override Guid? GetNullableGuid(string name)
        {
            return (Guid?)GetValue(name);
        }

        /// <summary>
        /// Gets the Int32 result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override int GetInt32(string name)
        {
            return (int)GetValue(name);
        }

        /// <summary>
        /// Gets the Nullable{Int32} result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override int? GetNullableInt32(string name)
        {
            return (int?)GetValue(name);
        }

        /// <summary>
        /// Gets the Int64 result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override long GetInt64(string name)
        {
            return (long)GetValue(name);
        }

        /// <summary>
        /// Gets the Nullable{Int64} result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override long? GetNullableInt64(string name)
        {
            return (long?)GetValue(name);
        }

        /// <summary>
        /// Gets the string result with specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override string GetString(string name)
        {
            return (string)GetValue(name);
        }

        /// <summary>
        /// Gets the result with specified name.
        /// </summary>
        public override object GetValue(string name)
        {
            object value;
            TryGetValue(name, out value);
            return value;
        }

        /// <summary>
        /// Determines whether the current instance contains result with the specified name.
        /// </summary>
        [DebuggerStepThrough]
        public override bool ContainsName(string name)
        {
            object value;
            return TryGetValue(name, out value);
        }
    }

    /// <summary>
    /// <see cref="TypedRecord"/> implementation uses <see cref="IRecord"/> instance as 
    /// source for it's data.
    /// </summary>
    public sealed class WrapperTypedRecord : TypedRecordBase
    {
        private readonly IRecord _InnerRecord;

        /// <summary>
        /// Initializes a new instance of the <see cref="WrapperTypedRecord"/> class.
        /// </summary>
        [DebuggerStepThrough]
        public WrapperTypedRecord(IRecord record)
        {
            if (record == null)
                throw new ArgumentNullException("record");
            _InnerRecord = record;
        }

        /// <summary>
        /// Try to get result with specified name. If result with such name exisits, method returns true,
        /// otherwise false. Found result is returned via <paramref name="result"/> output parameter.
        /// </summary>
        /// <param name="name">The case-insensitive result name.</param>
        /// <param name="result">The result with specified name or null if no result with specified name found.</param>
        /// <returns>
        ///     <c>true</c> if result with such name exists, <c>false</c> otherwise.
        /// </returns>
        [DebuggerStepThrough]
        public override bool TryGetValue(string name, out object result)
        {
            return _InnerRecord.TryGetValue(name, out result);
        }
    }

    /// <summary>
    /// <see cref="TypedRecord"/> implementation uses set of <see cref="KeyValuePair{TKey, TValue}"/> 
    /// as its data.
    /// </summary>
    public sealed class DictionaryTypedRecord : TypedRecordBase
    {
        private readonly Dictionary<string, object> _Data;

        /// <summary>
        /// Initializes a new instance of the <see cref="DictionaryTypedRecord"/> class.
        /// </summary>
        public DictionaryTypedRecord(Dictionary<string, object> data)
        {
            if (data == null)
                throw new ArgumentNullException("data");
            _Data = data;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DictionaryTypedRecord"/> class.
        /// </summary>
        public DictionaryTypedRecord(params KeyValuePair<string, object>[] values)
        {
            if (values == null)
                throw new ArgumentNullException("values");
            _Data = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
            foreach (var record in values)
                _Data.Add(record.Key, record.Value);
        }

        /// <summary>
        /// Gets the result with specified name.
        /// </summary>
        public sealed override object GetValue(string name)
        {
            return _Data[name];
        }

        /// <summary>
        /// Determines whether the current instance contains result with the specified name.
        /// </summary>
        public sealed override bool ContainsName(string name)
        {
            return _Data.ContainsKey(name);
        }

        /// <summary>
        /// Try to get result with specified name. If result with such name exisits, method returns true,
        /// otherwise false. Found result is returned via <paramref name="result"/> output parameter.
        /// </summary>
        /// <param name="name">The case-insensitive result name.</param>
        /// <param name="result">The result with specified name or null if no result with specified name found.</param>
        /// <returns>
        ///     <c>true</c> if result with such name exists, <c>false</c> otherwise.
        /// </returns>
        public override bool TryGetValue(string name, out object result)
        {
            return _Data.TryGetValue(name, out result);
        }
    }

    /// <summary>
    /// <see cref="TypedRecord"/> implementation uses two records and returns 
    /// result from primary record, if result with specified name does exist, returns result from secondary record.
    /// </summary>
    public sealed class CompositeRecord : TypedRecordBase
    {
        private readonly IRecord _Primary;
        private readonly IRecord _Secondary;

        /// <summary>
        /// Initializes a new instance of the <see cref="CompositeRecord"/> class.
        /// </summary>
        /// <param name="primary">The primary typed record.</param>
        /// <param name="secondary">The secondary typed record.</param>
        public CompositeRecord(IRecord primary, IRecord secondary)
        {
            if (primary == null)
                throw new ArgumentNullException("primary");
            _Primary = primary;
            if (secondary == null)
                throw new ArgumentNullException("secondary");
            _Secondary = secondary;
        }

        /// <summary>
        /// Try to get result with specified name. If result with such name exisits, method returns true,
        /// otherwise false. Found result is returned via <paramref name="result"/> output parameter.
        /// </summary>
        /// <param name="name">The case-insensitive result name.</param>
        /// <param name="result">The result.</param>
        /// <returns>
        ///     <c>true</c> if result with such name exists, <c>false</c> otherwise.
        /// </returns>
        public override bool TryGetValue(string name, out object result)
        {
            if (_Primary.TryGetValue(name, out result))
                return true;
            return _Secondary.TryGetValue(name, out result);
        }
    }
}
